package tool

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/user"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

// StringExpression replace the python String like "%(var)s" to string
type StringExpression struct {
	env map[string]string // the environment variable used to replace the var in the python expression
}

// NewStringExpression create a new StringExpression with the environment variables
func NewStringExpression(envs ...string) *StringExpression {
	se := &StringExpression{env: make(map[string]string)}

	for _, env := range os.Environ() {
		t := strings.SplitN(env, "=", 2)
		se.env["ENV_"+t[0]] = t[1]
	}
	n := len(envs)
	for i := 0; i+1 < n; i += 2 {
		se.env[envs[i]] = envs[i+1]
	}

	hostname, err := os.Hostname()
	if err == nil {
		se.env["host_node_name"] = hostname
	}

	return se

}

// Add add the environment variable (key,value)
func (se *StringExpression) Add(key string, value string) *StringExpression {
	se.env[key] = value
	return se
}

// Eval evaluate the expression include "%(var)s"  and return the string after replacing the var
func (se *StringExpression) Eval(s string) (string, error) {
	for {
		// find variable start indicator
		start := strings.Index(s, "%(")

		if start == -1 {
			return s, nil
		}

		end := start + 1
		n := len(s)

		// find variable end indicator
		for end < n && s[end] != ')' {
			end++
		}

		// find the type of the variable
		typ := end + 1
		for typ < n && !((s[typ] >= 'a' && s[typ] <= 'z') || (s[typ] >= 'A' && s[typ] <= 'Z')) {
			typ++
		}

		// evaluate the variable
		if typ < n {
			varName := s[start+2 : end]

			varValue, ok := se.env[varName]

			if !ok {
				return "", fmt.Errorf("fail to find the environment variable %s", varName)
			}
			if s[typ] == 'd' {
				i, err := strconv.Atoi(varValue)
				if err != nil {
					return "", fmt.Errorf("can't convert %s to integer", varValue)
				}
				s = s[0:start] + fmt.Sprintf("%"+s[end+1:typ+1], i) + s[typ+1:]
			} else if s[typ] == 's' {
				s = s[0:start] + varValue + s[typ+1:]
			} else {
				return "", fmt.Errorf("not implement type:%v", s[typ])
			}
		} else {
			return "", fmt.Errorf("invalid string expression format")
		}
	}

}

func GetIncludeFiles(configFileDir string, env *StringExpression, files []string) []string {
	result := make([]string, 0)
	// env := NewStringExpression("here", configFileDir)
	for _, fRaw := range files {
		targetPath := configFileDir
		f := fRaw
		if env != nil {
			ff, err := env.Eval(fRaw)
			if err != nil {
				continue
			}
			f = ff
		}
		if strings.HasPrefix(f, "./") || strings.HasPrefix(f, "../") || !strings.Contains(f, "/") {
			targetPath = filepath.Join(configFileDir, f)
		} else {
			targetPath = f
		}
		if !strings.Contains(targetPath, "*") && !strings.Contains(targetPath, "?") {
			result = append(result, targetPath)
			continue
		}
		fileInfos, err := ioutil.ReadDir(filepath.Dir(targetPath))
		if err == nil {
			goPattern := toRegexp(filepath.Base(f))
			for _, fileInfo := range fileInfos {
				if matched, err := regexp.MatchString(goPattern, fileInfo.Name()); matched && err == nil {
					result = append(result, filepath.Join(filepath.Dir(targetPath), fileInfo.Name()))
				}
			}
		}
	}
	return result
}

// convert supervisor file pattern to the go regrexp
func toRegexp(pattern string) string {
	tmp := strings.Split(pattern, ".")
	for i, t := range tmp {
		s := strings.Replace(t, "*", ".*", -1)
		tmp[i] = strings.Replace(s, "?", ".", -1)
	}
	return strings.Join(tmp, "\\.")
}

func pathSplit(path string) []string {
	r := make([]string, 0)
	curPath := path
	for {
		dir, file := filepath.Split(curPath)
		if len(file) > 0 {
			r = append(r, file)
		}
		if len(dir) <= 0 {
			break
		}
		curPath = dir[0 : len(dir)-1]
	}
	for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
		r[i], r[j] = r[j], r[i]
	}
	return r
}

// PathExpand replaces the ~ with user home directory
func PathExpand(path string) (string, error) {
	pathList := pathSplit(path)

	if len(pathList) > 0 && len(pathList[0]) > 0 && pathList[0][0] == '~' {
		var usr *user.User
		var err error

		if pathList[0] == "~" {
			usr, err = user.Current()
		} else {
			usr, err = user.Lookup(pathList[0][1:])
		}

		if err != nil {
			return "", err
		}
		pathList[0] = usr.HomeDir
		return filepath.Join(pathList...), nil
	}
	return path, nil
}
